﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web.Mvc;

namespace MvcContrib.Shp.UI.Menu
{
    public class MenuItem
    {
        private readonly IDictionary<string, object> _listAttributes = new Dictionary<string, object>();
        private readonly IDictionary<string, object> _itemAttributes = new Dictionary<string, object>();
        private readonly IDictionary<string, object> _anchorAttributes = new Dictionary<string, object>();
        private readonly IDictionary<string, object> _iconAttributes = new Dictionary<string, object>();

        private readonly ViewContext _viewContext;
        private readonly MenuSettings _menuSettings;
        private MenuItemBuilder _menuItemBuilder;
        private bool _displayText = true;

        public MenuItem(ViewContext viewContext, MenuSettings settings)
        {
            _viewContext = viewContext;
            _menuSettings = settings;
            MenuItems = new List<MenuItem>();
        }

        protected ViewContext ViewContext
        {
            get { return _viewContext; }
        }

        protected MenuSettings MenuSettings
        {
            get { return _menuSettings; }
        }

        public string Text { get; set; }
        public string Url { get; set; }
        public string IconUrl { get; set; }
        public string Target { get; set; }
        public string Title { get; set; }
        public string SelectedClass { get; set; }
        public bool Disable { get; set; }
        public string DisabledClass { get; set; }
        public bool DisplayWhenDisabled { get; set; }
        public bool DisplayText { get { return _displayText; } set { _displayText = value; } }

        public Func<object, object> ContentTemplate { get; set; }

        internal List<MenuItem> MenuItems { get; set; }

        public MenuItem ListAttributes(IDictionary<string, object> attributes)
        {
            foreach (var attribute in attributes)
            {
                _listAttributes.Add(attribute);
            }
            return this;
        }

        public MenuItem ItemAttributes(IDictionary<string, object> attributes)
        {
            foreach (var attribute in attributes)
            {
                _itemAttributes.Add(attribute);
            }
            return this;
        }

        public MenuItem AnchorAttributes(IDictionary<string, object> attributes)
        {
            foreach (var attribute in attributes)
            {
                _anchorAttributes.Add(attribute);
            }
            return this;
        }

        public MenuItem IconAttributes(IDictionary<string, object> attributes)
        {
            foreach (var attribute in attributes)
            {
                _iconAttributes.Add(attribute);
            }
            return this;
        }

        public MenuItem Items(Action<MenuItemBuilder> menuItemBuilder)
        {
            if (_menuItemBuilder == null)
                _menuItemBuilder = new MenuItemBuilder(this, _viewContext, MenuSettings);
            menuItemBuilder(_menuItemBuilder);

            return this;
        }

        protected virtual bool DisplayChildren()
        {
            // this method has a flaw - if a parent includes more than one disabled children, it will render the ul container.
            // rather than loop thru all children and then return the flag to display children, the assumption here is that
            // if a menu item has more than one child menu item, at least one will be enabled.  this is for performance
            // reasons - may want to change this later to fully navigate the children and return the approriate flag.

            if (Disable || (MenuItems.Count == 0)) return false;

            if (MenuItems.Count == 1 && !MenuItems[0].IncludeItem()) return false;

            return true;
          
            //return ((MenuItems.Count > 0) && !Disable);
        }

        protected void DisableChildren()
        {
            if (!Disable || !DisplayChildren()) return;

            foreach (var child in MenuItems)
            {
                child.Disable(true);
                child.DisplayWhenDisabled = true;
            }
        }

        protected virtual string GetDisabledClass()
        {
            return !string.IsNullOrWhiteSpace(DisabledClass) ? DisabledClass : _menuSettings.DisabledMenuItemClass;
        }

        protected virtual string GetSelectedClass()
        {
            return !string.IsNullOrWhiteSpace(SelectedClass) ? SelectedClass : _menuSettings.SelectedMenuItemClass;
        }

        protected virtual bool IsSelected()
        {
            return string.CompareOrdinal(ViewContext.HttpContext.Request.Path, Url) == 0;
        }

        public virtual IDictionary<string, object> GetListAttributes()
        {
            return _listAttributes;
        }

        public virtual IDictionary<string, object> GetItemAttributes()
        {
            return _itemAttributes;
        }

        public virtual IDictionary<string, object> GetAnchorAttributes()
        {
            return _anchorAttributes;
        }

        public virtual IDictionary<string, object> GetIconAttributes()
        {
            return _iconAttributes;
        }
        
        protected virtual bool IncludeItem()
        {
            return !(Disable && !DisplayWhenDisabled);
        }

        protected virtual void PrepareAttributes()
        {
            PrepareDisabledItem();
            PrepareSelectedItem();

            if (string.IsNullOrWhiteSpace(Url))
                Url = "#";

            _anchorAttributes["href"] = Url;
            
            // set the title/tooltip
            if (!string.IsNullOrWhiteSpace(Title))
                _anchorAttributes["title"] = Title;

            // set the target
            if (!string.IsNullOrWhiteSpace(Target))
                _anchorAttributes["target"] = Target;
        }

        protected virtual void PrepareDisabledItem()
        {
            if (!Disable) return;

            _anchorAttributes.MergeAttributes(GetDisabledClass(), "class", true);

            Url = "#";
        }

        protected virtual void PrepareSelectedItem()
        {
            if(IsSelected())
                _anchorAttributes.MergeAttributes(GetSelectedClass(), "class", true);
        }

        /// <summary>
        /// Renders the icon to the MenuItem.
        /// </summary>
        /// <param name="builder">The builder.</param>
        protected virtual void RenderIcon(StringBuilder builder)
        {
            if (string.IsNullOrWhiteSpace(IconUrl)) return;

            _iconAttributes.ReplaceAttribute(IconUrl, "src");

            var iconAttributes = BuildHtmlAttributes(_iconAttributes);

            if (iconAttributes.Length > 0) iconAttributes = " " + iconAttributes;

            builder.AppendFormat("<img{0}/>", iconAttributes);
        }

        /// <summary>
        /// Renders the full menu item.
        /// </summary>
        /// <returns></returns>
        public virtual string RenderItem()
        {
            if (!IncludeItem()) return null;

            PrepareAttributes();

            var itemAttributes = BuildHtmlAttributes(_itemAttributes);
            var anchorAttributes = BuildHtmlAttributes(_anchorAttributes);

            // create a space between the opening element tag and the beginning of the 
            // element's attributes (e.g. <li class="">)
            if (itemAttributes.Length > 0) itemAttributes = " " + itemAttributes;
            if (anchorAttributes.Length > 0) anchorAttributes = " " + anchorAttributes;

            var builder = new StringBuilder();

            builder.AppendFormat("<li{0}>", itemAttributes);

            builder.AppendFormat("<a{0}>", anchorAttributes);
            RenderIcon(builder);
            builder.Append(Text);
            builder.Append("</a>");

            if (RenderContent(builder))
                return builder.ToString();

            // TemplateMenuItem (item templates) do not support sub menus - just ignore the sub menu items if present
            if (DisplayChildren())
            {
                DisableChildren();

                var listAttributes = BuildHtmlAttributes(GetListAttributes());
                if (listAttributes.Length > 0) listAttributes = " " + listAttributes;

                builder.AppendFormat("<ul{0}>", listAttributes);

                foreach (var menuItem in MenuItems)
                {
                    builder.Append(menuItem.RenderItem());
                }
                builder.Append("</ul>");
            }
            builder.Append("</li>");

            return builder.ToString();
        }

        /// <summary>
        /// Renders the Content Template (not the content of the TemplateMenuItem).
        /// </summary>
        /// <param name="builder">The builder.</param>
        /// <returns></returns>
        protected bool RenderContent(StringBuilder builder)
        {
            // process a content menu item
            if (ContentTemplate != null)
            {
                var html = ContentTemplate(new object()).ToString();
                builder.Append(html);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Converts the specified attributes dictionary of key-value pairs into a string of HTML attributes. 
        /// </summary>
        /// <returns></returns>
        protected string BuildHtmlAttributes(IDictionary<string, object> attributes)
        {
            return DictionaryExtensions.ToHtmlAttributes(attributes);
        }
    }

    public static class MenuItemExtensions
    {
        /// <summary>
        /// Item Attributes - used to add attribues to the current menu item (e.g. &lt;li&gt; element).
        /// </summary>
        /// <returns>The current MenuItem instance.</returns>
        public static MenuItem ItemAttributes(this MenuItem menuItem, params Func<object, object>[] hash)
        {
            return menuItem.ItemAttributes(new Hash(hash));
        }

        /// <summary>
        /// Anchor Attributes - used to add attribues to the current menu item's anchor (e.g. &lt;a&gt; element).
        /// </summary>
        /// <returns>The current MenuItem instance.</returns>
        public static MenuItem AnchorAttributes(this MenuItem menuItem, params Func<object, object>[] hash)
        {
            return menuItem.AnchorAttributes(new Hash(hash));
        }

        /// <summary>
        /// Icon Attributes - used to add attribues to the current menu item's icon (e.g. &lt;img&gt; element).
        /// </summary>
        /// <returns>The current MenuItem instance.</returns>
        public static MenuItem IconAttributes(this MenuItem menuItem, params Func<object, object>[] hash)
        {
            return menuItem.IconAttributes(new Hash(hash));
        }

        /// <summary>
        /// List Attributes - used to add attribues to the current menu item's child list (e.g. &lt;ul&gt; element).
        /// </summary>
        /// <returns>The current MenuItem instance.</returns>
        public static MenuItem ListAttributes(this MenuItem menuItem, params Func<object, object>[] hash)
        {
            return menuItem.ListAttributes(new Hash(hash));
        }

        /// <summary>
        /// Sets the current menu item's Text.
        /// </summary>
        /// <returns>The current MenuItem instance.</returns>
        public static MenuItem Text(this MenuItem menuItem, string text)
        {
            menuItem.Text = text;
            return menuItem;
        }

        public static MenuItem Title(this MenuItem menuItem, string title)
        {
            menuItem.Title = title;
            return menuItem;
        }

        /// <summary>
        /// Sets the current menu item's URL - applied to the child anchor element.  This takes precedence over adding href via the AnchorAttributes collection.
        /// </summary>
        /// <returns>The current MenuItem instance.</returns>
        public static MenuItem Url(this MenuItem menuItem, string url)
        {
            menuItem.Url = url;
            return menuItem;
        }

        /// <summary>
        /// Sets the anchor's target for the current menu item.
        /// </summary>
        /// <param name="menuItem">The menu item.</param>
        /// <param name="target">The target.</param>
        /// <returns></returns>
        public static MenuItem Target(this MenuItem menuItem, string target)
        {
            menuItem.Target = target;
            return menuItem;
        }

        /// <summary>
        /// Sets the current menu item's Selected Class.
        /// </summary>
        /// <returns>The current MenuItem instance.</returns>
        public static MenuItem SelectedClass(this MenuItem menuItem, string selectedClass)
        {
            menuItem.SelectedClass = selectedClass;
            return menuItem;
        }

        /// <summary>
        /// Disables/Enables the current menu item.
        /// </summary>
        /// <param name="menuItem">The menu item.</param>
        /// <param name="disable">if set to <c>true</c> disables the current MenuItem instance.</param>
        /// <returns></returns>
        public static MenuItem Disable(this MenuItem menuItem, bool disable)
        {
            menuItem.Disable = disable;
            return menuItem;
        }

        /// <summary>
        /// Sets the current menu item's Disabled Class.
        /// </summary>
        /// <returns>The current MenuItem instance.</returns>
        public static MenuItem DisabledClass(this MenuItem menuItem, string disabledClass)
        {
            menuItem.DisabledClass = disabledClass;
            return menuItem;
        }

        /// <summary>
        /// Display the current menu item when it is disabled - default is false (do not show).
        /// </summary>
        /// <param name="menuItem">The menu item.</param>
        /// <param name="display">if set to <c>true</c> the current MenuItem instance will be displayed when it is disabled.</param>
        /// <returns></returns>
        public static MenuItem DisplayWhenDisabled(this MenuItem menuItem, bool display)
        {
            menuItem.DisplayWhenDisabled = display;
            return menuItem;
        }

        /// <summary>
        /// Overrides the rendering of the current menu item's Text property - default is true (display the text).
        /// </summary>
        /// <remarks>
        /// This propery is only applicable for <see cref="ActionMenuItem{T}"/> derived menu items.
        /// </remarks>
        /// <param name="menuItem">The menu item.</param>
        /// <param name="display">if set to <c>false</c>, the Text propery of the current MenuItem instance will not be displayed.</param>
        /// <returns></returns>
        public static MenuItem DisplayText(this MenuItem menuItem, bool display)
        {
            menuItem.DisplayText = display;
            return menuItem;
        }

        /// <summary>
        /// Adds a Content Template to the current menu item.
        /// </summary>
        /// <remarks>
        /// The difference between Content Template and a <see cref="TemplateMenuItem"/>  
        /// is that the Content Template is responsible for rendering the content container,
        /// while a <see cref="TemplateMenuItem"/> is wrapped in a 
        /// &lt;ul&gt;&lt;li&gt;&lt;/li&gt;&lt;/ul&gt; element sequence.
        /// </remarks>
        /// <param name="menuItem">The menu item.</param>
        /// <param name="template">The template.</param>
        /// <returns></returns>
        public static MenuItem Content(this MenuItem menuItem, Func<object, object> template)
        {
            menuItem.ContentTemplate = template;
            return menuItem;
        }
    }
}
